package com.shangcg.util;

import com.shangcg.service.CallableService;
import com.shangcg.service.SellTicket;
import com.shangcg.service.impl.SellTicketImpl;
import com.shangcg.service.impl.SellTicketImplWithCallable;
import org.springframework.util.StringUtils;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多线程卖票测试服务
 * 1 多个线程同时卖，不用线程池查看结果 ： new Thread
 * 2 多个线程同时卖，用线程池，查看结果   new ThreadExecutors()
 * 3 多个线程同时卖票，线程池的方式，且指定线程池的名称  new ThreadExecutors()
 * 4 两个线程 轮流卖票，一个线程卖一张后下一个线程卖，比如A -> B 轮流交替 自旋 + lock + condition
 * 5 两个线程轮流卖票，用线程池方式，票卖完提示结束  自旋 + lock + condition +资源服务返回值
 * 6 10个线程轮流卖票，一个线程卖1张结束后第二线程开始卖，依此类推直到第十个线程结束 thread.join()
 * 7 保安打开大门， 2个线程同时开始卖票 ，此场景阻塞的是卖票卖票窗口
 * 8 所有卖票窗口都卖票结束了，保安才能下班
 * 9 2个卖票窗口，线程1是老人专用窗口，线程2是普通窗口 threadLocal
 *
 * 10 semaphore acquire()、release() 信号量 用作限流
 * 11 CyclicBarrier 加法计数器、核心方法 await() await数量达到初始化CyclicBarrier数量则触发CyclicBarrier召唤主流程
 * 12 submit future
 * 13 callable  两个窗口，利用callable创建两个线程，并返回执行结果
 *    callable与 executorService.submit()方法异曲同工，线程池的submit底层也是将callable封装为futureTask
 * 14 new ThreadPool()
 * 15 exchanger
 * 16 读写锁 readwriteLock 卖票服务提供余票查询方法，支持多个线程同时读（查），卖票只能一个线程写（卖）
 *    独占锁（写锁）  共享锁（读锁）
 *
 *
 */
public class SellTicketUtilWithLock {


    //1 多个线程同时卖票
    public void setSellTicketTest(){

        //3个线程竞争同一个sellTicket资源对象，一起卖票
        SellTicket sellTicket = new SellTicketImpl();
        new Thread(() ->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        }, "A").start();
        new Thread(() ->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        }, "B").start();
        new Thread(() ->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        }, "C").start();

    }

    //

    /**
     * 线程池的方式卖票，每执行一个execute,就相当于创建一个线程
     *
     *  poolExecutor.execute(()->{
     *             for (int i = 0; i < 500; i++){
     *                 sellTicket.sell();
     *             }
     *  }); 等价于
     *  poolExecutor.execute(new Runnable() {
     *             @Override
     *             public void run() {
     *                 for (int i = 0; i < 500; i++){
     *                     sellTicket.sell();
     *                 }
     *             }
     *  });
     *
     *
     */
    //2 多个线程同时卖票， 用线程池的方式
    public void sellTicketTestWithThreadPool(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,100,100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(200), new ThreadPoolExecutor.AbortPolicy());

        SellTicket sellTicket = new SellTicketImpl();
        poolExecutor.execute(()->{
            for (int i = 0; i < 500; i++){
                sellTicket.sellWithLock();
            }
        });

        poolExecutor.execute(()->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        });

        poolExecutor.execute(()->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        });
    }

    //3 多个线程同时卖票，线程池的方式，且指定线程池的名称
    public void SellTicketWithThreadPoolAndName(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(20, 100, 100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(200),new MyThreadNameFactory("Sell-Ticket"), new ThreadPoolExecutor.AbortPolicy());

        SellTicket sellTicket = new SellTicketImpl();
        poolExecutor.execute(() ->{
            for (int i = 0; i < 200; i++){
                sellTicket.sellWithLock();
            }
        });

        poolExecutor.execute(() ->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        });

        poolExecutor.execute(() ->{
            for (int i = 0; i < 50; i++){
                sellTicket.sellWithLock();
            }
        });
    }


    //4 两个线程 轮流卖票，一个线程卖一张后下一个线程卖，比如A -> B 轮流交替
    public void sellTicketSwapWithThreadPool(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 100,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(200),new MyThreadNameFactory("SellTicket Swap "), new ThreadPoolExecutor.AbortPolicy());

        SellTicket sellTicket = new SellTicketImpl();

        Lock lock = new ReentrantLock();
        Condition conditionWait = lock.newCondition();

        poolExecutor.execute(() ->{
            while (true) {
                lock.lock();
                sellTicket.sellWithLock();
                try {
                    Thread.sleep(100);
                    conditionWait.signal();
                    conditionWait.await();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();

                }
            }
        });

        poolExecutor.execute(() ->{
            while (true){
                lock.lock();
                sellTicket.sellWithLock();
                try {
                    Thread.sleep(100);
                    conditionWait.signal();
                    conditionWait.await();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }

        });
    }

    //5 两个线程轮流卖票，用线程池方式，票卖完提示结束
    public void sellTicketSwapWithThreadPoolAndFinish(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 20,100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),new MyThreadNameFactory("Sell Ticket"),new ThreadPoolExecutor.AbortPolicy());

        SellTicket sellTicket = new SellTicketImpl();
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        poolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                while(true){
                    lock.lock();
                    try {
                        boolean sold = sellTicket.sellWithReturn1();
                        if (!sold){
                            System.out.println("卖票结束");
                            break;
                        }
                        condition.signal();
                        condition.await();
                    }catch (InterruptedException e){
                       e.printStackTrace();
                    }finally{
                        lock.unlock();
                    }
                }
            }
        });

        poolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                while(true){
                    lock.lock();
                    try {
                        boolean sold = sellTicket.sellWithReturn1();
                        if (!sold){
                            System.out.println("卖票结束");
                            break;
                        }
                        condition.signal();
                        condition.await();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }finally{
                        lock.unlock();
                    }
                }
            }
        });
    }


    /**
     *  6  10个线程轮流卖票， 主线程负责创建自线程1-10，之后子线程1、2、3...10的顺序各自卖一张系统结束
     *  详细： main线程创建子线程A, 子线程中调用main的线程对象 thread.join()，则子线程A被挂起，
     *       直到main线程执行结束，后自动挂起结束执行子线程A的逻辑。
     *       此处main是被调用线程，及thread对象， A是调用线程， 阻塞调用线程，直到被调用线程执行结束
     */
    //TODO 此处牵扯父子线程传值问题， 该方法传的前一个线程信息对象
    public void sellSwapWithJoin(){
        System.out.println("主线程开始");
        Thread previousThread = Thread.currentThread();
        SellTicket sellTicket = new SellTicketImpl();
        for (int i = 0 ;i < 10; i++){
            Thread finalParentThread = previousThread;
            Thread childThread = new Thread(() -> {
                try {
                    finalParentThread.join();
                    sellTicket.sellWithLock();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            });

            childThread.start();
            previousThread = childThread;
        }
        System.out.println("主线程结束");
    }

    //7 保安打开大门， 所有线程同时开始卖票 ，此场景阻塞的是卖票卖票窗口，
    //  即所有卖票线程调用countDown.await()，保安调用countDown.countDown()让计数器归零
    public void sellTicketWaitAllBegin(){

        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,10,100,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20));

        CountDownLatch countDownLatch = new CountDownLatch(1);
        SellTicket sellTicket = new SellTicketImpl();
        poolExecutor.execute(() ->{
            System.out.println("我是卖票窗口1，我已准备就绪,马上被阻塞进入等待");
            try {
                countDownLatch.await();
                System.out.println("我是卖票窗口1，我已经被唤醒，开始卖票");
                sellTicket.sellWithLock();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });

        poolExecutor.execute(() ->{
            System.out.println("我是卖票窗口2，我已准备就绪,马上被阻塞进入等待");
            try {
                countDownLatch.await();
                System.out.println("我是卖票窗口2，我已经被唤醒，开始卖票");
                sellTicket.sellWithLock();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });

        try {
            Thread.sleep(100);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("我是主线程管理员，我要打开大门了");
        countDownLatch.countDown();
    }
    //8 所有卖票窗口都卖票结束了，保安才能下班
    public void sellTicketBlockMainWithCountDown(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 10, 100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20),new MyThreadNameFactory("sell ticket with countDown"));
        SellTicket sellTicket = new SellTicketImpl();
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Future<Boolean> submit1 = poolExecutor.submit(() -> {
            sellTicket.sellWithLock();
            countDownLatch.countDown();
            System.out.println("我是窗口1，我卖票结束了");
            return true;
        });

        //TODO 这种方式怎么加返回值
//        boolean t = false;
//        Future<Boolean> submit = poolExecutor.submit(new Runnable() {
//            @Override
//            public void run() {
//
//            }
//        }, t);

        Future<Boolean> submit2 = poolExecutor.submit(() -> {
            sellTicket.sellWithLock();
            countDownLatch.countDown();
            System.out.println("我是窗口2，我卖票结束了");
            return true;
        });
        poolExecutor.shutdown();
        try {
            System.out.println("我是保安， 即将被阻塞，直到所有窗口卖票结束");
            countDownLatch.await();
            System.out.println("我是保安， 我已被唤醒，关大门");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }


    //9 2个卖票窗口，线程1是老人专用窗口，线程2是普通窗口
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public void sellTicketWithThreadLocal(){
        threadLocal.set("我是主线程管理员张三");
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,10,100,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20));

        SellTicket sellTicket = new SellTicketImpl();
        poolExecutor.execute(()->{
            if (StringUtils.isEmpty(threadLocal.get())){
                System.out.println("不知道主线程管理员是谁");
            }
            threadLocal.set("老人专用窗口");
            sellTicket.sellWithLockAndThreadLocal();
        });

        poolExecutor.execute(()->{
            if (StringUtils.isEmpty(threadLocal.get())){
                System.out.println("不知道主线程管理员是谁");
            }
            threadLocal.set("普通窗口");
            sellTicket.sellWithLockAndThreadLocal();
        });
    }


    //13 2个卖票窗口， 使用callable创建线程， 获取每个线程卖票的数量
    public  void sellTicketWithCallable() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 10, 100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20));


        CallableService<Integer> callableService = new SellTicketImplWithCallable<>();
        FutureTask futureTask = new FutureTask(callableService);
        new Thread(futureTask, "A").start();
        Object o = futureTask.get();
        System.out.println(o);

        CallableService<Integer> callableServiceB = new SellTicketImplWithCallable<>();
        FutureTask futureTaskB = new FutureTask(callableServiceB);
        new Thread(futureTaskB, "B").start();
        Object ob = futureTask.get();
        System.out.println(ob);

    }


    // 16
    public void ReadWriteLockTest(){
        SellTicket sellTicket = new SellTicketImpl();
        //10个窗口同时 读取剩余票数量
        for(int i = 0; i < 10; i++){
            new Thread(() ->{
                sellTicket.getSellNumber();
            }).start();
        }

        //10个窗口同时卖票，卖票采用读写锁的写锁
        for (int i = 0; i < 10; i++){
            new Thread(() -> {
                sellTicket.sellTicketWithWriteLock();
            }).start();
        }
    }





    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SellTicketUtilWithLock sellTicketUtil = new SellTicketUtilWithLock();
//        sellTicketUtil.setSellTicketTest();
//        sellTicketUtil.sellTicketTestWithThreadPool();
//        sellTicketUtil.SellTicketWithThreadPoolAndName();
//        sellTicketUtil.sellTicketSwap();
//        sellTicketUtil.sellTicketSwapWithThreadPool();
//        sellTicketUtil.sellTicketSwapWithThreadPoolAndFinish();
//        sellTicketUtil.sellSwapWithJoin();
//        sellTicketUtil.sellTicketWaitAllBegin();
//        sellTicketUtil.sellTicketBlockMainWithCountDown();
//        sellTicketUtil.sellTicketWithThreadLocal();
//        sellTicketUtil.sellTicketWithCallable();
        sellTicketUtil.ReadWriteLockTest();
    }



}
